package com.tony.blog.server.lucene;

import cn.hutool.core.collection.CollectionUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import com.tony.blog.api.utils.Constant;
import com.tony.blog.api.utils.PageUtil;
import com.tony.blog.model.entity.BlogEntity;
import com.tony.blog.server.service.web.BlogService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.document.StringField;
import org.apache.lucene.index.*;
import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.highlight.*;
import org.apache.lucene.store.FSDirectory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.wltea.analyzer.lucene.IKAnalyzer;

import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
 * Created with IntelliJ IDEA.
 *
 * @Title: LuceneBlogService
 * @Auther: 皮蛋布丁
 * @Date: 2021/09/27/19:14
 * @Description: 针对blog博文数据表建立的Luncene操作服务类
 */
@Component
@Slf4j
public class LuceneBlogService {

    @Autowired
    private Environment env;
    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private BlogService blogService;

    //ik中文分词器实例
    private static final Analyzer analyzer = new MyIKAnalyzer();

    /**
    * @Description: createDocument 1 创建索引库(对博文数据进行分词，插入底层lucene索引库、建立映射)
    * @Param: [blog]
    * @return: void
    * @Author: 皮蛋布丁
    * @Date: 2021/9/28 9:47
    */
    public void createDocument(final BlogEntity blog) throws Exception {
        if (Objects.isNull(blog)) {
            return;
        }
        IndexWriter writer = null;
        try {
            //创建索引的存储目录与分词器类型、索引写入器配置类
            FSDirectory directory =
                    FSDirectory.open(Paths.get(env.getProperty("lucene.index.directory")));
            IndexWriterConfig config = new IndexWriterConfig();

            //设置创建索引的模式(在原来的索引基础上创建或者追加)
            config.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND);

            //创建索引写入器
            writer = new IndexWriter(directory,config);

            //创建文档数据(相当于博文表中的一条一条的数据)
            Document doc = new Document();

            FieldType fieldType = new FieldType();
            fieldType.setIndexOptions(IndexOptions.DOCS);
            fieldType.setStored(true);

            //给文件添加字段，并指定每个字段:是否分词 - 按照前端需要展示什么，就尽可能将字段数据塞入到Lucene存储起来
            doc.add(new StringField("id",String.valueOf(blog.getId()), Field.Store.YES));
            doc.add(new StringField("content",blog.getContent(), Field.Store.YES));
            doc.add(new StringField("userId",String.valueOf(blog.getUserId()), Field.Store.YES));
            doc.add(new StringField("type",String.valueOf(blog.getType()), Field.Store.YES));
            doc.add(new StringField("blogId",String.valueOf(Objects.isNull(blog.getBlogId()) ?
                    -1 : blog.getBlogId()),
                    Field.Store.YES));
            doc.add(new StringField("createTime", Constant.DATE_TIME_FORMAT.format(blog.getCreateTime()),
                    Field.Store.YES));
            doc.add(new StringField("isActive",String.valueOf(blog.getIsActive()), Field.Store.YES));
            doc.add(new StringField("isDelete",String.valueOf(blog.getIsDelete()), Field.Store.YES));

            doc.add(new Field("userName",String.valueOf(blog.getUserName()), fieldType));
            doc.add(new Field("nickName",String.valueOf(blog.getNickName()), fieldType));
            doc.add(new Field("headerImg", StringUtils.isBlank(blog.getHeaderImg()) ? "" :
                    blog.getHeaderImg(),fieldType));

            doc.add(new StringField("imgUrls", CollectionUtil.isEmpty(blog.getImgUrls()) ? "" :
                    objectMapper.writeValueAsString(blog.getImgUrls()),
                    Field.Store.YES));

            //写入索引库、提交事务
            writer.addDocument(doc);
            writer.commit();
            writer.close();

            log.info("成功为当前微博数据创建了文档，创建了分词-文档的索引映射！");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (writer != null) {
                writer.close();
            }
        }
    }

    /**
    * @Description: deleteDocument 删除底层索引库的博文的文档数据
    * @Param: [id]
    * @return: java.lang.Boolean
    * @Author: 皮蛋布丁
    * @Date: 2021/9/28 16:35
    */
    public Boolean deleteDocument(final Integer id) throws Exception {
        if (Objects.isNull(id)) {
            return false;
        }

        IndexWriter writer = null;
        try {
            //创建索引的存储目录与分词器类型、索引写入器配置类
            FSDirectory directory = FSDirectory.open(Paths.get(env.getProperty("lucene.index" +
                    ".directory")));
            IndexWriterConfig config = new IndexWriterConfig(analyzer);

            //设置创建索引的模式(在原来的索引基础上创建或者追加)
            config.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND);

            //创建索引写入器
            writer = new IndexWriter(directory,config);

            writer.deleteDocuments(new Term("id",String.valueOf(id)));
            writer.close();
            log.info("成功删除了当前微博数据的文档及映射");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (writer != null) {
                writer.close();
            }
        }
        return true;
    }

    /**
    * @Description: searchDocumentPage 前端首页全文检索(分页)
    * @Param: [queryText, pageSize, currPage]
    * @return: com.tony.blog.api.utils.PageUtil
    * @Author: 皮蛋布丁
    * @Date: 2021/9/28 13:26
    */
    public PageUtil searchDocumentPage(String queryText, final int pageSize, final int currPage) throws Exception {
        List<BlogEntity> list = Lists.newLinkedList();
        //创建索引的存储目录与分词器类型、索引写入器配置类
        FSDirectory directory = FSDirectory.open(Paths.get(env.getProperty("lucene.index" +
                ".directory")));
        DirectoryReader reader = DirectoryReader.open(directory);
        IndexSearcher searcher = new IndexSearcher(reader);

        //查询解析器
        String[] fields = {"content","userName","nickName"};
        MultiFieldQueryParser parser = new MultiFieldQueryParser(fields,analyzer);
        Query query = parser.parse(queryText);

        //执行搜索
        TopDocs docs = searcher.search(query,Integer.MAX_VALUE);

        //获取搜索得到的/命中的文档总数
        Long total = docs.totalHits.value;
        ScoreDoc[] scoreDocs = docs.scoreDocs;
        log.info("本次共搜索到：{}条文档数据！",total);

        //确定分页数据的起始位置和终止位置，如：total=32条，pageSize=10条
        //第一页：start=0，end=9；第二页：start=0，end=19；第三页：start=0，end=29;第四页：start=0，end=32
        int start = (currPage-1)*pageSize;
        int end = Math.min(start + pageSize,scoreDocs.length);

        BlogEntity blog;
        Document searchDoc;
        //遍历得到的文档数据，进行封装处理
        for (int i = start; i < end; i++) {
            searchDoc = searcher.doc(scoreDocs[i].doc);
            if (!Objects.isNull(searchDoc) && StringUtils.isNotBlank(searchDoc.get("id"))) {
                try {
                    blog = new BlogEntity();
                    blog.setId(Integer.valueOf(searchDoc.get("id")));

                    //内容-v1.0
                    //blog.setContent(searchDoc.get("content"));

                    //内容(高亮显示)-V2.0
                    blog.setContent(highLightContent(query,"content",searchDoc.get("content")));

                    blog.setUserId(Long.valueOf(searchDoc.get("userId")));
                    blog.setType(Integer.valueOf(searchDoc.get("type")));
                    blog.setBlogId(Integer.valueOf(searchDoc.get("blogId")));
                    blog.setCreateTime(Constant.DATE_TIME_FORMAT.parse(searchDoc.get("createTime")));
                    blog.setIsActive(Integer.valueOf(searchDoc.get("isActive")));
                    blog.setIsDelete(Integer.valueOf(searchDoc.get("isDelete")));

                    blog.setUserName(searchDoc.get("userName"));
                    blog.setNickName(searchDoc.get("nickName"));
                    blog.setHeaderImg(searchDoc.get("headerImg"));

                    blog.setImgUrls(StringUtils.isBlank(searchDoc.get("imgUrls")) ?
                            Collections.emptyList() : objectMapper.readValue(searchDoc.get(
                                    "imgUrls"),List.class));

                    //对微博数据做二次处理
                    blogService.commonManageSingleBlog(blog);

                    list.add(blog);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        directory.close();
        return new PageUtil(list,total.intValue(),pageSize,currPage);
    }

    /**
    * @Description: highLightContent 高亮处理指定字段的内容
    * @Param: [query, fieldName, fieldContent]
    * @return: java.lang.String
    * @Author: 皮蛋布丁
    * @Date: 2021/9/28 17:37
    */
    private static String highLightContent(Query query,final String fieldName,
                                           String fieldContent) throws Exception {
        //设置高亮标签，可自定义，此处用html将其显示为红色
        SimpleHTMLFormatter formatter = new SimpleHTMLFormatter("<font color='red'>", "</font>");
        //得分
        QueryScorer scorer = new QueryScorer(query);
        //创建 段标签fragment
        Fragmenter fragmenter = new SimpleSpanFragmenter(scorer);
        //高分分析器
        Highlighter highlighter = new Highlighter(formatter,scorer);
        highlighter.setTextFragmenter(fragmenter);
        //取得所有的片段
        String[] fragments = highlighter.getBestFragments(analyzer,fieldName,fieldContent,10000);
        return (!Objects.isNull(fragments) && fragments.length > 0) ? fragments[0] : fieldContent;
    }
}
